package com.neusoft.databus.agent.trap;

import java.io.IOException;
import java.net.UnknownHostException;
import java.util.Date;
import java.util.Optional;
import java.util.Vector;

import org.snmp4j.CommandResponder;
import org.snmp4j.CommandResponderEvent;
import org.snmp4j.MessageDispatcherImpl;
import org.snmp4j.PDU;
import org.snmp4j.Snmp;
import org.snmp4j.TransportMapping;
import org.snmp4j.mp.MPv1;
import org.snmp4j.mp.MPv2c;
import org.snmp4j.mp.MPv3;
import org.snmp4j.mp.SnmpConstants;
import org.snmp4j.security.SecurityModels;
import org.snmp4j.security.SecurityProtocols;
import org.snmp4j.security.USM;
import org.snmp4j.smi.Address;
import org.snmp4j.smi.GenericAddress;
import org.snmp4j.smi.OID;
import org.snmp4j.smi.OctetString;
import org.snmp4j.smi.TcpAddress;
import org.snmp4j.smi.UdpAddress;
import org.snmp4j.smi.VariableBinding;
import org.snmp4j.transport.DefaultTcpTransportMapping;
import org.snmp4j.transport.DefaultUdpTransportMapping;
import org.snmp4j.util.MultiThreadedMessageDispatcher;

import com.neusoft.bizcore.common.utils.DateUtil;
import com.neusoft.databus.common.bean.SnmptrapBean;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class SnmptrapServer implements CommandResponder {

    private MultiThreadedMessageDispatcher dispatcher;
    private Snmp snmp = null;
    private Address listenAddress;
    private ThreadPool threadPool;
    private final SnmptrapProcessor snmptrapProcessor;

    public SnmptrapServer(final String localAddress, final int port, final int threadPoolSize,
            final SnmptrapProcessor processor)
            throws Exception {
        this.snmptrapProcessor = processor;
        this.init(localAddress, port, threadPoolSize);
    }

    private void init(final String localAddress, final int port, final int threadPoolSize)
            throws UnknownHostException, IOException {
        this.threadPool = ThreadPool.create("snmp-trap-server", threadPoolSize);
        this.dispatcher = new MultiThreadedMessageDispatcher(this.threadPool, new MessageDispatcherImpl());
        this.listenAddress = GenericAddress.parse("udp:" + localAddress + "/" + port);
        TransportMapping<?> transport;
        if (this.listenAddress instanceof UdpAddress) {
            transport = new DefaultUdpTransportMapping((UdpAddress) this.listenAddress);
        } else {
            transport = new DefaultTcpTransportMapping((TcpAddress) this.listenAddress);
        }
        this.snmp = new Snmp(this.dispatcher, transport);
        this.snmp.getMessageDispatcher().addMessageProcessingModel(new MPv1());
        this.snmp.getMessageDispatcher().addMessageProcessingModel(new MPv2c());
        this.snmp.getMessageDispatcher().addMessageProcessingModel(new MPv3());
        final USM usm = new USM(SecurityProtocols.getInstance(), new OctetString(MPv3.createLocalEngineID()), 0);
        SecurityModels.getInstance().addSecurityModel(usm);

    }

    public void start() throws Exception {
        this.snmp.listen();
        this.snmp.addCommandResponder(this);
    }

    @Override
    @SuppressWarnings("unchecked")
    public void processPdu(final CommandResponderEvent event) {

        if (SnmptrapServer.log.isDebugEnabled()) {
            SnmptrapServer.log.debug("snmp trap server {} receive trap event: {}", Thread.currentThread().getName(),
                    event);
        }
        if (event.getPDU() == null) {
            return;
        }
        final Vector<VariableBinding> recVBs = (Vector<VariableBinding>) event.getPDU().getVariableBindings();
        if (recVBs.size() < 9) {
            SnmptrapServer.log.warn("the trap variable bindings is less than 9.");
            return;
        }

        final SnmptrapBean snmpTrapBean = new SnmptrapBean();
        final OID anyOID = recVBs.get(0).getOid();
        snmpTrapBean.setSubSystemNo(String.valueOf(anyOID.get(6)));

        if ((PDU.TRAP == event.getPDU().getType())) {
            final Optional<VariableBinding> vb =
                    recVBs.stream().filter(it -> SnmpConstants.snmpTrapOID.equals(it.getOid())).findAny();
            if (vb.isPresent()) {
                snmpTrapBean.setSubSystemNo(String.valueOf(((OID) vb.get().getVariable()).get(6)));
            }
        }

        recVBs.stream().forEach(vb -> {
            if (SnmpConstants.sysUpTime.equals(vb.getOid()) || SnmpConstants.snmpTrapOID.equals(vb.getOid())) {
                return;
            }
            final int lastIndexValue = vb.getOid().get(vb.getOid().size() - 1);
            String variableValue;
            if (vb.getVariable() instanceof OctetString) {
                final OctetString os = (OctetString) vb.getVariable();
                variableValue = new String(os.getValue());
            } else {
                variableValue = vb.getVariable().toString();
            }

            switch (lastIndexValue) {
            case 2:
                snmpTrapBean.setResourceName(variableValue);
                break;
            case 3:
                snmpTrapBean.setResourceId(snmpTrapBean.getSubSystemNo() + "_" + variableValue);
                break;
            case 4:
                snmpTrapBean.setMessageType(variableValue);
                break;
            case 5:
                snmpTrapBean.setAlarmGrade(variableValue);
                break;
            case 6:
                Date reportDate = null;
                try {
                    reportDate = DateUtil.string2Date(variableValue, DateUtil.PATTERN_STANDARD);
                } catch (final RuntimeException e) {
                    reportDate = new Date();
                }
                snmpTrapBean.setReportTime(reportDate);
                break;
            case 7:
                snmpTrapBean.setMetricName(variableValue);
                break;
            case 8:
                snmpTrapBean.setMetricVariable(variableValue);
                break;
            case 9:
                snmpTrapBean.setContent(variableValue);
                break;
            case 10:
                snmpTrapBean.setAlarmType(variableValue);
                break;
            case 11:
            	snmpTrapBean.setConfirmer(variableValue);
            	break;
            default:
                break;
            }

        });

        final StringBuilder idBuilder = new StringBuilder();
        idBuilder.append(snmpTrapBean.getSubSystemNo()).append(snmpTrapBean.getResourceId())
                .append(snmpTrapBean.getMetricVariable())
                .append(snmpTrapBean.getAlarmType());
        snmpTrapBean.setIdentifier(idBuilder.toString().hashCode());
        //2019-11-22 由于葛旬设计蓄电池组维度拆的过细，databus接收告警增加分组判读，不具备通用性
        if ("47061".equals(snmpTrapBean.getSubSystemNo())) {
            final String content = snmpTrapBean.getContent();
            snmpTrapBean.setResourceId(snmpTrapBean.getResourceId() + "_" + content.charAt(3));
        }
        this.snmptrapProcessor.process(snmpTrapBean);
    }

}
